/*
 * Copyright 2015 CPE Lyon - IRC 2013/2016 - Cours de programmation réseau - Groupe H
 *
 * This file is part of BOTT.
 *
 * BOTT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * BOTT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with BOTT. If not, see http://www.gnu.org/licenses/.
 */

/*
 * Client de messagerie.
 *
 * Permet de se connecter à un serveur et à des salons.
 */

#include <errno.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "fonctions-client.h"
#include "protocole.h"
#include "fonctions.h"
#include "client.h"

/* Arguments : IP du serveur et nom d'utilisateur */
int main (int argc, char *argv[]) {

    printf("\033[H\033[2J");
    fflush(stdout);

    /* Vérification du nombre d'arguments  */
    if (argc < 3) {
        fprintf(stderr, "%s\e[1mIncorrect parameter(s)\n", KRED);
        printf("%sUsage: ./client IP USERNAME\n", KBLU);
        printf("Exemple: ./client 127.0.0.1 martin\n");
        clearColors();
        return EXIT_FAILURE;
    }

    pthread_t threadTreatment;
    struct sockaddr_in serverAddr, clientAddr;
    memset(&serverAddr, 0, sizeof serverAddr);
    socklen_t addrLen;
    struct timeval tv;
    int socketDesc, validName, leave;
    char *command, *roomName, *newRoom;
    room roomToSwitch, emptyRoom, roomToJoin;
    emptyRoom.idRoom = 0;
    /* Le salon courant */
    room currentRoom = emptyRoom;
    /* Liste des salons auxquels l'utilisateur est connecté */
    room listRooms[MAX];

    /* Vérification des arguments  */
    char *username = argv[2];
    for (int i = 0; i < (int) strlen(username); i++) {
        if (!isalnum(username[i])) {
            fprintf(stderr, "%s\e[1mInvalid username (only alphanumeric characters are allowed)\n", KRED);
            clearColors();
            return EXIT_FAILURE;
        }
    }
    if (inet_pton(AF_INET, argv[1], &(serverAddr.sin_addr)) != 1) {
        fprintf(stderr, "%s\e[1mInvalid IP address\n", KRED);
        clearColors();
        return EXIT_FAILURE;
    }

    /* Initialisation */
    for (int i = 0; i < MAX; i++) {
        listRooms[i] = emptyRoom;
    }

    /* Création du socket */
    if ((socketDesc = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("Socket creation error");
        return 1;
    }

    /* Attacher */
    clientAddr.sin_family = AF_INET;
    clientAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    clientAddr.sin_port = htons(0);
    if (bind(socketDesc, (struct sockaddr *) &clientAddr, sizeof clientAddr) == -1) {
        perror("Socket bind error");
        return 1;
    }

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);

    /* Ensemble des descripteurs de fichiers en lecture */
    fd_set rfds;
    /* Vidage de l'ensemble */
    FD_ZERO(&rfds);
    /* On ajoute 0 (stdin) dans l'ensemble */
    FD_SET(0, &rfds);
    /* On ajoute le socket dans l'ensemble */
    FD_SET(socketDesc, &rfds);

    /* Connexion au serveur */
    int idUser = doConnect(socketDesc, &serverAddr, username);

    if (idUser != 0) {

        signal(SIGINT, clearColors);
        setBackground("40");
        printf("%s%s[ No chat joined ]\n", KWHT, BBLU);
        printf("\e[0m");
        printf("%s\e[1mSuccessfully connected\n", KBLU);
        printf("%s\e[1mYou can now join a chat.\n\n", KBLU);
        aliveData transmitData;
        transmitData.socketDesc = socketDesc;
        transmitData.serverAddr = serverAddr;
        transmitData.idUser = idUser;

        /* Lancement du processus gérant le ALIVE */
        if (pthread_create(&threadTreatment, NULL, checkIfAlive, &transmitData) != 0) {
                perror("Thread creation error");
                return EXIT_FAILURE;
        }
    }

    while (idUser) {

        int retval;
        char buffer[BUFFER];
        char originalMessage[BUFFER];
        FD_ZERO(&rfds);
        FD_SET(0, &rfds);
        FD_SET(socketDesc, &rfds);
        memset(buffer, 0, BUFFER);
        tv.tv_sec = 10;
        tv.tv_usec = 0;

        /* Attente bloquante infinie d'arrivée de données sur les descripteurs */
        retval = select(socketDesc + 1, &rfds, NULL, NULL, &tv);
        if (retval == -1) {

            if (errno != EINTR) {
                perror("Select error");
            }
            return EXIT_FAILURE;

        } else if (retval == 0) {

            /* Timeout dépassé pour le ACKALIVE */
            fprintf(stderr, "%s\e[1mConnection with server lost!\n", KRED);
            printf("%s\n", KBLU);
            close(socketDesc);
            clearColors();
            return EXIT_FAILURE;

        } else if (retval) {

            /* Des données sont disponibles sur l'entrée standard */
            if (FD_ISSET(0, &rfds)) {

                if (read(0, buffer, BUFFER) != -1) {
                    strcpy(originalMessage, buffer);
                    command = strtok(buffer, " ");

                    /* Demande de connexion à un salon */
                    if (strcmp(command, "/join") == 0 || strcmp(command, "/j") == 0) {

                        roomName = strtok(NULL, "\n");
                        if (roomName[0] == '#' && strlen(roomName) > 1) {
                            validName = 1;
                            for (int i = 1; i < (int) strlen(roomName); i++) {
                                if (!isalnum(roomName[i])) {
                                    validName = 0;
                                    break;
                                }
                            }
                            if (validName) {
                                roomToJoin = doJoin(socketDesc, &serverAddr, roomName, idUser, listRooms, currentRoom);
                                if (roomToJoin.idRoom != currentRoom.idRoom) {
                                    currentRoom = roomToJoin;
                                    displayChat(listRooms, currentRoom);
                                    printf("Chat %s joined!\n", roomName);
                                }
                            } else {
                                fprintf(stderr, "%s\e[1mIncorrect chat name (must start with # and contains only alphanumeric characters)\n", KRED);
                                printf("%s\n", KBLU);
                            }
                        } else {
                            fprintf(stderr, "%s\e[1mIncorrect chat name (must start with # and contains only alphanumeric characters)\n", KRED);
                            printf("%s\n", KBLU);
                        }

                    /* Demande de déconnexion d'un salon */
                    } else if (strcmp(command, "/leave\n") == 0 || strcmp(command, "/l\n") == 0) {

                        leave = doLeave(socketDesc, &serverAddr, idUser, listRooms, currentRoom);
                        if (leave) {
                            currentRoom = emptyRoom;
                            displayChat(listRooms, currentRoom);
                            printf("You have left the chat!\n\n");
                            printf("You can now join or switch to another chat!\n");
                        }

                    /* Demande de changement de salon */
                    } else if (strcmp(command, "/switch") == 0 || strcmp(command, "/s") == 0) {

                        newRoom = strtok(NULL, "\n");
                        if (newRoom[0] == '#' && strlen(newRoom) > 1) {
                            validName = 1;
                            for (int i = 1; i < (int) strlen(newRoom); i++) {
                                if (!isalnum(newRoom[i])) {
                                    validName = 0;
                                    break;
                                }
                            }
                            if (validName) {
                                roomToSwitch = getRoomByName(newRoom, listRooms);
                                if (roomToSwitch.idRoom != 0 && roomToSwitch.idRoom == currentRoom.idRoom) {
                                    fprintf(stderr, "%s\e[1mYou are already in this chat\n", KRED);
                                    printf("%s\e[1m\n", KBLU);
                                } else if (roomToSwitch.idRoom != 0) {
                                    currentRoom = roomToSwitch;
                                    displayChat(listRooms, currentRoom);
                                } else {
                                    fprintf(stderr, "%s\e[1mYou are not connected to this chat\n", KRED);
                                    printf("%s\e[1m\n", KBLU);
                                }
                            } else {
                                fprintf(stderr, "%s\e[1mIncorrect chat name (must start with # and contains only alphanumeric characters)\n", KRED);
                                printf("%s\n", KBLU);
                            }
                        } else {
                            fprintf(stderr, "%s\e[1mIncorrect chat name (must start with # and contains only alphanumeric characters)\n", KRED);
                            printf("%s\n", KBLU);
                        }

                    /* Demande du fermeture du programme */
                    } else if (strcmp(command, "/quit\n") == 0 || strcmp(command, "/q\n") == 0) {

                        doDisconnect(socketDesc, &serverAddr, idUser);
                        idUser = 0;

                    /* Afficher l'aide */
                    } else if (strcmp(command, "/help\n") == 0 || strcmp(command, "/h\n") == 0) {

                        printf("Available commands:\n");
                        printf("\t[/join #chatName | /j #chatName] Join the specified chat\n");
                        printf("\t[/switch #chatName | /s #chatName] Switch to the specified chat\n");
                        printf("\t[/leave | /l] Leave the current chat\n");
                        printf("\t[/quit | /q] Quit the program\n");

                    } else {

                        if (currentRoom.idRoom == 0) {
                            fprintf(stderr, "%s\e[1mIncorrect command\n", KRED);
                            printf("%s\n", KBLU);
                        } else {
                            /* Envoyer un message */
                            if (strlen(originalMessage) > 0 && strcmp(originalMessage, "\n") != 0) {
                                strtok(originalMessage, "\n");
                                doSay(socketDesc, &serverAddr, idUser, listRooms, currentRoom, originalMessage);
                                displayChat(listRooms, currentRoom);
                            }
                        }

                    }
                } else {
                    perror("Read error");
                    return EXIT_FAILURE;
                }
            }

            /* Des données sont disponibles via le socket */
            if (FD_ISSET(socketDesc, &rfds)) {
                bimp_msg receivedData;
                if (recvfrom(socketDesc, &receivedData, sizeof receivedData, 0, (struct sockaddr *) &serverAddr, &addrLen) != -1) {
                    if (checkServerCommand(receivedData.idCommand)) {
                        /* Un message a été reçu */
                        if (receivedData.idCommand == 102) {
                            treatTell(receivedData, listRooms);
                            displayChat(listRooms, currentRoom);
                        }
                    }
                } else {
                    perror("Data reception error");
                    return EXIT_FAILURE;
                }
            }
        }
    }

    clearColors();
    return EXIT_SUCCESS;
}